package bsh;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import bsh.compilation.DebugOptions;
import bsh.exception.CompilationAssertionFailed;
import bsh.exception.CompilationFailedException;
import bsh.meta.JavaClassWrapper;
import bsh.meta.MethodDescriptor;
import bsh.meta.MethodScope;
import bsh.meta.Scope;
import bsh.meta.ScopeType;
import bsh.meta.TypeWrapper;

/**
 * Compilador para o return, break e continue
 * 
 * @author takeshi
 */
public class ReturnStatementCompiler implements NodeCompiler {

    private final BSHReturnStatement node;

    private final CompilationUnit unit;

    /**
     * @param node node
     * @param unit unit
     */
    public ReturnStatementCompiler(BSHReturnStatement node, CompilationUnit unit) {
        super();
        this.node = node;
        this.unit = unit;
    }

    /**
     * {@inheritDoc}
     */
    public void compile(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        state.addLineNumber(host, this.node.getLineNumber());
        state.assertEmptyStack();
        Scope scope;
        switch (this.node.kind) {
        case BSHReturnStatement.BREAK:
            scope = state.findEnclosingScopeWithType(ScopeType.LOOP_BLOCK);
            if (scope == null) {
                throw new CompilationFailedException(this.node.getLineNumber(), "Trying to break without enclosing loop");
            }
            host.visitJumpInsn(Opcodes.GOTO, scope.getEndLabel());
            break;

        case BSHReturnStatement.CONTINUE:
            scope = state.findEnclosingScopeWithType(ScopeType.LOOP_BLOCK);
            if (scope == null) {
                throw new CompilationFailedException(this.node.getLineNumber(), "Trying to break without enclosing loop");
            }
            host.visitJumpInsn(Opcodes.GOTO, scope.getStartLabel());
            break;

        case BSHReturnStatement.RETURN:
            this.compileReturnStatement(host, state);
            break;

        default:
            throw new CompilationAssertionFailed("unknown kind: " + this.node.kind);
        }
    }

    /**
     * Compila um return. Tenta fazer a coercao de tipos para o tipo que o metodo contido devolve
     * 
     * @param host {@link MethodVisitor}
     * @param state {@link CompilationState}
     * @throws CompilationFailedException e
     */
    private void compileReturnStatement(MethodVisitor host, CompilationState state) throws CompilationFailedException {
        state.assertEmptyStack();
        MethodScope method = state.findEnclosingMethod();
        TypeWrapper returnType = method.getMethod().returnType();
        if (state.getUnit().options().debugOptions().contains(DebugOptions.VIRTUAL_STACK_FRAMES)) {
            host.visitVarInsn(Opcodes.ALOAD, 0);
            state.pushStack(CompiledScript.class);
            MethodDescriptor descriptor = unit.reflectionUtils().getMethodWithName(CompiledScript.class, "popMethod");
            descriptor.writeInvoke(host, state);
        }
        if (JavaClassWrapper.VOID_WRAPPER.equals(returnType)) {
            if (this.node.jjtGetNumChildren() > 0) {
                throw new CompilationFailedException(this.node.getLineNumber(), "trying to return something from a 'void' method");
            }
            host.visitInsn(Opcodes.RETURN);
            // must return null;
        } else {
            if (this.node.jjtGetNumChildren() > 0) {
                this.unit.getNodeCompiler(this.node.getChild(0)).compile(host, state);
            } else {
                host.visitInsn(Opcodes.ACONST_NULL);
                state.pushNull();
            }
            this.unit.getCompilerManager().coerceToType(host, state.peekStack(), returnType, state);
            if (!returnType.isSuperTypeOf(state.peekStack())) {
                throw new CompilationFailedException(this.node.getLineNumber(), "Cannot return type: " + state.peekStack()
                        + " from a method that returns: " + returnType);
            }
            state.popStack();
            host.visitInsn(returnType.toASMType().getOpcode(Opcodes.IRETURN));
        }
    }

    /**
     * {@inheritDoc}
     */
    public TypeWrapper nodeResultType(Scope scope) throws CompilationFailedException {
        throw new CompilationAssertionFailed("cannot find the type of a return statement");
    }

}
